import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { DataSource, Repository } from "typeorm";
import { arrConvert2Tree, isNone, objConvert } from "st-common-core";
import { ResType } from "st-common-req";
import { findFromDBAndCache, find, save, remove } from "st-common-server";
import { RedisKeyConst, set } from "../common/redis";
import { Transaction } from "../common/transaction";
import { Menu } from "./entities/Menu";
import { MenuEntityPropNameEnum } from "./enums/menu-entity-prop-name.enum";
import { MenuFilterFieldNameEnum } from "./enums/menu-filter-field-name.enum";
import { MenuVo } from "./vos/menu.vo";
import { MenuVerifyUtil } from "./utils/menu-verify.util";
import { MenuResultMessage } from "./consts/menu-result-message";

/**
 * 菜单服务
 */
@Injectable()
export class MenuService {

  constructor(
    @InjectRepository(Menu)
    private readonly menuRepository: Repository<Menu>,
    private readonly dataSource: DataSource
  ) {
  }

  /**
   * 从数据库中查询所有未被删除的菜单的所有信息，并将菜单列表转换为树形结构的 MenuVo 对象列表，然后将其缓存到 Redis 中
   *
   * @param {boolean} isTree 是否返回树形结构的菜单列表，默认为 true
   * @returns {Promise<Array<MenuVo> | null>} MenuCompletedVo 对象列表，如果发生了错误则返回 null
   */
  async findAllFromDBAndCache(isTree: boolean = true): Promise<Array<MenuVo> | null> {
    // 从数据库中查询所有菜单，并缓存到 Redis 中
    const menuVos = await findFromDBAndCache<Menu, MenuVo>({
      repository: this.menuRepository,
      convertHandler: (data) => objConvert<MenuVo>(data, new MenuVo()),
      cacheOptions: {
        key: RedisKeyConst.MENU_ALL()
      }
    }) as MenuVo[];
    if (isNone(menuVos)) return null;
    // 将菜单列表转换为树形结构
    const menuVosTree: MenuVo[] = arrConvert2Tree(menuVos, MenuEntityPropNameEnum.ID, MenuEntityPropNameEnum.PARENT_ID).tree;
    // 将树形结构的菜单列表缓存到 Redis 中
    const result = await set({
      key: RedisKeyConst.MENU_ALL_TREE(),
      value: menuVosTree
    });
    // 返回菜单列表，如果缓存失败则返回 null
    return result ? (isTree ? menuVosTree : menuVos) : null;
  }

  /**
   * 根据过滤条件查询菜单<br/>
   * 如果在 Redis 中缓存了菜单信息列表，则直接从缓存中获取，否则从数据库中查询，并将菜单列表转换为树形结构并缓存到 Redis 中
   *
   * @returns {Promise<Array<MenuVo> | null>} 树形结构的菜单列表，如果发生了错误则返回 null
   */
  async find(filters: Record<string, any>, isTree: boolean = true): Promise<Array<MenuVo> | null> {
    // 查询过滤菜单
    const menuVos = await find<MenuVo>({
      filters,
      filterFieldNames: Object.values(MenuFilterFieldNameEnum),
      likeFieldNames: [
        MenuFilterFieldNameEnum.NAME,
        MenuFilterFieldNameEnum.PAGE_PATH,
        MenuFilterFieldNameEnum.ROUTE_NAME,
        MenuFilterFieldNameEnum.ROUTE_PATH
      ] as string[],
      cacheKeyWithoutFilter: RedisKeyConst.MENU_ALL_TREE(),
      findFromDBWithoutFilter: async () => await this.findAllFromDBAndCache(isTree),
      cacheKey: RedisKeyConst.MENU_ALL(),
      findFromDB: async () => await this.findAllFromDBAndCache(false)
    });
    // 如果没有过滤条件，返回的就直接是树形结构的菜单
    if (Object.keys(filters).length <= 0) return menuVos;
    // 返回菜单列表
    return isTree ?
      arrConvert2Tree(menuVos, MenuEntityPropNameEnum.ID, MenuEntityPropNameEnum.PARENT_ID).tree :
      menuVos;
  }

  /**
   * 保存菜单信息，并更新 Redis 中缓存的菜单列表
   *
   * @param {Menu} menu 要保存的菜单信息
   * @returns {Promise<{status: boolean, message: string}>} 保存结果，status 为 true 表示保存成功，否则表示失败，
   * message 为保存结果提示信息
   */
  @Transaction()
  async save(menu: Menu): Promise<ResType> {
    // 校验并保存菜单
    return await save<Menu>({
      data: menu,
      repository: this.menuRepository,
      verify: MenuVerifyUtil,
      saveSuccessHandler: async () => !isNone(await this.findAllFromDBAndCache()),
      successMessage: MenuResultMessage.SAVE_SUCCESS,
      failMessage: MenuResultMessage.SAVE_FAIL
    });
  }

  /**
   * 根据菜单 id 列表删除指定的菜单，并更新 Redis 中缓存的菜单列表，只有当前所有要删除的菜单都删除成功后，才会返回删除
   * 菜单成功，否则返回失败，并回滚事务
   *
   * @param {number[]} ids 要删除的菜单 id 列表
   * @param {boolean} isCascade 是否级联删除，即是否删除要删除菜单下的子菜单
   * @returns {Promise<{message: string, status: boolean}>} 删除结果，status 为 true 表示删除成功，否则表示失败，
   */
  @Transaction()
  async remove(ids: number[], isCascade: boolean = false): Promise<ResType> {
    return await remove({
      removeHandler: async () => {
        // 遍历删除菜单（逻辑删除）
        while (ids.length > 0) {
          // 获取要删除菜单列表的队头元素
          const id = ids.shift();
          // 查询当前菜单下子菜单
          const childMenus = await this.menuRepository.find({
            where: {
              parentId: id
            }
          });
          // 如果要级联删除当前菜单下的子菜单
          if (isCascade) {
            // 当前菜单下的子菜单的 id 加入待删除列表
            ids.push(...childMenus.map(menu => menu[MenuEntityPropNameEnum.ID]));
          }
          // 如果仅删除当前菜单
          else {
            // 如果当前菜单下有子菜单
            if (childMenus.length > 0) {
              // 将当前菜单下的子菜单的 parentId 改为 null
              await this.menuRepository.save(childMenus.map(menu => {
                menu[MenuEntityPropNameEnum.PARENT_ID] = null;
                return menu;
              }));
            }
          }
          // 删除当前要删除的菜单
          const res = await this.menuRepository.softDelete(id);
          // 如果删除当前要删除的菜单失败
          if (!res.affected) return false;
        }
        return true;
      },
      removeSuccessHandler: async () => !isNone(await this.findAllFromDBAndCache()),
      successMessage: MenuResultMessage.DEL_SUCCESS,
      failMessage: MenuResultMessage.DEL_FAIL
    });
  }

}
